﻿using System;
using System.Reflection;
using System.Collections.Generic;

namespace EnterpriseFramework.DataAccess
{
    public static class TableFactory
    {
        private const BindingFlags FLAGS = BindingFlags.Public | BindingFlags.NonPublic
            | BindingFlags.Instance | BindingFlags.Static;
        private const BindingFlags FLAGS_2 = FLAGS | BindingFlags.DeclaredOnly;

        /// <summary>
        /// Builds a table model for a given type.
        /// </summary>
        /// <param name="type">type for which to build a table model</param>
        /// <returns>table model for a given type</returns>
        public static Table Build(Type type)
        {
            // 2. Get the table information.
            //if(!type.IsDefined(typeof(TableAttribute), false))
            //throw new DeclarationException("Type " + type.Name + " does not have the Table attribute.");
            //ConstructorInfo dd;

            TableAttribute tableAttr = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), false)[0];

            // TableAttribute can be without a name, so take the name of the type.
            if (string.IsNullOrEmpty(tableAttr.Name))
                tableAttr.Name = type.Name;

            List<AbstractColumn> columns = new List<AbstractColumn>();

            // 4. Process properties, fields, and methods of given type and base types, if needed.
            Type currentType = type;
            TableAttribute currentTable = tableAttr;
            do
            {
                ProcessProperties(currentType, columns);
                ProcessFields(currentType, columns);

                currentType = currentType.BaseType;
                if (currentType.IsDefined(typeof(TableAttribute), false))
                    currentTable = (TableAttribute)currentType.GetCustomAttributes(typeof(TableAttribute), false)[0];
                else
                    currentTable = null;
            }
            while (currentTable != null);

            // 5. Create a table and return it.
            return new Table(tableAttr.Name, tableAttr.Schema, columns.ToArray(), tableAttr.ReadOnly);
        }

        /// <summary>
        /// Adds columns defined on properties in given type to given list.
        /// </summary>
        /// <param name="type">object type</param>
        /// <param name="list">column list</param>
        private static void ProcessProperties(Type type, List<AbstractColumn> list)
        {
            object[] array = null;
            PropertyInfo[] properties = type.GetProperties(FLAGS_2);
            foreach (PropertyInfo p in properties)
            {
                array = p.GetCustomAttributes(typeof(ColumnAttribute), false);
                if (array.Length == 0)
                    continue;

                ColumnAttribute columnAttr = (ColumnAttribute)array[0];
                if (string.IsNullOrEmpty(columnAttr.Name))
                    columnAttr.Name = p.Name;

                list.Add(
                    new PropertyColumn(p, columnAttr.Name, columnAttr.DBType,
                        columnAttr.Size, columnAttr.AllowNull, columnAttr.IsComputed, columnAttr.IsReadOnly,
                        columnAttr.PrimaryKey, columnAttr.AutoIncrement, columnAttr.IsUnique, columnAttr.Concurrency)
                );
            }
        }

        /// <summary>
        /// Adds columns defined on fields in given type to given list.
        /// </summary>
        /// <param name="type">object type</param>
        /// <param name="list">column list</param>
        private static void ProcessFields(Type type, List<AbstractColumn> list)
        {
            object[] array = null;
            FieldInfo[] fields = type.GetFields(FLAGS_2);
            foreach (FieldInfo f in fields)
            {
                array = f.GetCustomAttributes(typeof(ColumnAttribute), false);
                if (array.Length == 0)
                    continue;

                ColumnAttribute columnAttr = (ColumnAttribute)array[0];
                if (string.IsNullOrEmpty(columnAttr.Name))
                    columnAttr.Name = f.Name;

                list.Add(
                    new FieldColumn(f, columnAttr.Name, columnAttr.DBType,
                        columnAttr.Size, columnAttr.AllowNull, columnAttr.IsComputed, columnAttr.IsReadOnly,
                        columnAttr.PrimaryKey, columnAttr.AutoIncrement, columnAttr.IsUnique, columnAttr.Concurrency)
                );
            }
        }


    }
}
